package pl.edu.agh.pros.proxy.logic.dao.common;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import javax.jdo.Extent;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.appengine.api.datastore.Key;

/**
 * Basic operations of persisted objects. Every Data Access Objects should
 * extend this class.
 * 
 * @param <T>
 *            Persisted object type.
 */
public abstract class AbstractDAO<T> implements IAbstractDAO<T> {

	private static final Logger logger = LoggerFactory.getLogger(AbstractDAO.class);
	protected final PersistenceManagerFactory persistenceManagerFactory = PMF.getInstance();
	protected final Class<T> clazz;

	protected interface PersistenceOperation<P> {
		public P method(PersistenceManager persistenceManager);
	}

	protected abstract class VoidPersistenceOperation implements PersistenceOperation<Object> {
		@Override
		public Object method(PersistenceManager persistenceManager) {
			voidMethod(persistenceManager);
			return null;
		}

		public abstract void voidMethod(PersistenceManager persistenceManager);
	}

	protected <P> P persistenceOperation(PersistenceOperation<P> methodBody) {
		PersistenceManager persistenceManager = null;
		try {
			persistenceManager = persistenceManagerFactory.getPersistenceManager();
			return methodBody.method(persistenceManager);
		} catch (Exception e) {
			logger.error("Error during persistence operation", e);
			return null;
		} finally {
			try {
				if (persistenceManager != null)
					persistenceManager.close();
			} catch (Exception e) {
				logger.error("Could not close persistenceManager", e);
			}
		}
	}

	public AbstractDAO(Class<T> clazz) {
		this.clazz = clazz;
	}

	@Override
	public Collection<T> getAll() {
		return persistenceOperation(new PersistenceOperation<Collection<T>>() {
			@Override
			public Collection<T> method(PersistenceManager persistenceManager) {
				List<T> persistenceObjects = new ArrayList<T>();
				Extent<T> extent = persistenceManager.getExtent(clazz, false);
				for (T object : extent) {
					persistenceObjects.add(object);
				}
				extent.closeAll();
				return persistenceObjects;
			}
		});
	}

	@Override
	public T getObjectById(final Object key) {
		return persistenceOperation(new PersistenceOperation<T>() {
			@Override
			public T method(PersistenceManager persistenceManager) {
				return persistenceManager.detachCopy(persistenceManager.getObjectById(clazz, key.toString()));
			}
		});
	}

	@Override
	public T getObjectByKey(final Key key) {
		return persistenceOperation(new PersistenceOperation<T>() {
			@Override
			public T method(PersistenceManager persistenceManager) {
				return persistenceManager.detachCopy(persistenceManager.getObjectById(clazz, key));
			}
		});
	}

	@Override
	public Collection<T> getObjects(final Collection<?> keys) {
		return persistenceOperation(new PersistenceOperation<Collection<T>>() {
			@Override
			public Collection<T> method(PersistenceManager persistenceManager) {
				Collection<T> result = new LinkedList<T>();
				for (Object id : keys) {
					result.add(persistenceManager.detachCopy(persistenceManager.getObjectById(clazz, id.toString())));
				}
				return result;
			}
		});
	}

	@Override
	public Collection<T> getObjects(final String queryString, final String params, final Object... values) {
		return persistenceOperation(new PersistenceOperation<Collection<T>>() {
			@SuppressWarnings("unchecked")
			@Override
			public Collection<T> method(PersistenceManager persistenceManager) {
				Query query = persistenceManager.newQuery(clazz, queryString);
				query.declareParameters(params);
				return new ArrayList<T>((Collection<T>) query.executeWithArray(values));
			}
		});
	}

	@Override
	public T getObject(final String queryString, final String params, final Object... values) {
		return persistenceOperation(new PersistenceOperation<T>() {
			@SuppressWarnings("unchecked")
			@Override
			public T method(PersistenceManager persistenceManager) {
				Query query = persistenceManager.newQuery(clazz, queryString);
				query.declareParameters(params);
				try {
					return (new ArrayList<T>((Collection<T>) query.executeWithArray(values))).get(0);
				} catch (IndexOutOfBoundsException e) {
					return null;
				}
			}
		});
	}

	@Override
	public T save(final T object) {
		return persistenceOperation(new PersistenceOperation<T>() {
			@Override
			public T method(PersistenceManager persistenceManager) {
				return persistenceManager.makePersistent(object);
			}
		});
	}

	@Override
	public Collection<T> saveAll(final Collection<T> collection) {
		return persistenceOperation(new PersistenceOperation<Collection<T>>() {
			@Override
			public Collection<T> method(PersistenceManager persistenceManager) {
				return persistenceManager.makePersistentAll(collection);
			}
		});
	}

	@Override
	public void delete(final T object) {
		persistenceOperation(new VoidPersistenceOperation() {
			@Override
			public void voidMethod(PersistenceManager persistenceManager) {
				T persistent = persistenceManager.makePersistent(object);
				persistenceManager.deletePersistent(persistent);
			}
		});
	}

	@Override
	public void deleteByKey(final Object id) {
		persistenceOperation(new VoidPersistenceOperation() {
			@Override
			public void voidMethod(PersistenceManager persistenceManager) {
				persistenceManager.deletePersistent(persistenceManager.getObjectById(clazz, id));
			}
		});
	}

	@Override
	public void deleteAll() {
		persistenceOperation(new VoidPersistenceOperation() {
			@Override
			public void voidMethod(PersistenceManager persistenceManager) {
				Collection<T> col = getAll();
				col = persistenceManager.makePersistentAll(col);
				persistenceManager.deletePersistentAll(col);
			}
		});
	}

	@Override
	public void deleteAll(final Collection<T> collection) {
		persistenceOperation(new VoidPersistenceOperation() {
			@Override
			public void voidMethod(PersistenceManager persistenceManager) {
				persistenceManager.deletePersistentAll(persistenceManager.makePersistentAll(collection));
			}
		});
	}

}
